﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using RaisingStudio.Common.Web;

namespace RaisingStudio.Data.Linq
{
    public class SharePointExpressionCommandBuilder : CommandBuilder
    {
        private RaisingStudio.Data.Common.Builders.CommonCommandBuilder commandBuilder;
        public RaisingStudio.Data.Common.Builders.CommonCommandBuilder CommandBuilder
        {
            get { return commandBuilder; }
            set { commandBuilder = value; }
        }

        #region Constructor
        public SharePointExpressionCommandBuilder(System.Linq.Expressions.Expression expression) : base(expression)
        {            
        }

        public SharePointExpressionCommandBuilder(RaisingStudio.Data.Common.Builders.CommonCommandBuilder commonCommandBuilder, System.Linq.Expressions.Expression expression)
            : base(expression)
        {
            this.commandBuilder = commonCommandBuilder;
        }
        #endregion

        public static CommonCommand GetCommand(RaisingStudio.Data.Common.Builders.CommonCommandBuilder commandBuilder, System.Linq.Expressions.Expression expression, out string[] columns)
        {
            CommandSegmentType segmentType;
            return (new SharePointExpressionCommandBuilder(commandBuilder, expression)).GetCommand(out columns, out segmentType);
        }


        public CommandSegment GetCommand()
        {
            if (this.expression != null)
            {
                if (this.commandSegment == null)
                {
                    Build();
                }
                return this.commandSegment;
            }
            else
            {
                return null;
            }
        }

        public CommandSegment GetCommand(out RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            columns = null;
            if (this.expression != null)
            {
                if (this.commandSegment == null)
                {
                    Build();
                }
                columns = this.columnExpressions.ToArray();
                return this.commandSegment;
            }
            else
            {
                return null;
            }
        }


        public CommonCommand GetCommand(out CommandSegmentType segmentType)
        {
            CommandSegment commandSegment = this.GetCommand();
            segmentType = commandSegment.SegmentType;
            return commandSegment.CommonCommand;
        }

        public CommonCommand GetCommand(out RaisingStudio.Data.Expressions.ColumnExpression[] columns, out CommandSegmentType segmentType)
        {
            CommandSegment commandSegment = this.GetCommand(out columns);
            segmentType = commandSegment.SegmentType;
            return commandSegment.CommonCommand;
        }

        public CommonCommand GetCommand(out string[] columnNames)
        {
            CommandSegmentType segmentType;
            return this.GetCommand(out columnNames, out segmentType);
        }

        public CommonCommand GetCommand(out string[] columnNames, out CommandSegmentType segmentType)
        {
            RaisingStudio.Data.Expressions.ColumnExpression[] columns;
            CommonCommand command = this.GetCommand(out columns, out segmentType);
            columnNames = GetColumnNames(columns);
            return command;
        }



        private CommandSegment commandSegment;
        private Stack<CommandSegment> segmentStack = new Stack<CommandSegment>();
        private System.Data.Common.DataTableMapping dataTableMapping;

        public override void Build()
        {
            this.segmentStack.Clear();
            this.sortingExpressions.Clear();
            this.columnExpressions.Clear();
            this.parameterIndex = 0;

            if ((expression is MethodCallExpression) && ((expression as MethodCallExpression).Method.Name == "Count") && ((expression as MethodCallExpression).Arguments[0] is ConstantExpression))
            {
                CommonCommand command = this.commandBuilder.GetDataCountCommand();
                this.commandSegment = new CommandSegment(command, CommandSegmentType.Select);
            }
            else
            {
                ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator();
                Expression evaluatedExpression = expressionEvaluator.Evaluate(expression);

                this.Visit(evaluatedExpression);

                CommandSegmentType finalSegmentType = CommandSegmentType.None;
                CommonCommand command = null;
                CommandSegment commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                CommonCommand condition = null;
                if (commandSegment != null)
                {
                    if (commandSegment.SegmentType == CommandSegmentType.Select)
                    {
                        command = commandSegment.CommonCommand;
                        finalSegmentType |= CommandSegmentType.Select;

                        commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                        condition = GenerateConditionCommand(commandSegment);
                    }
                    else if ((commandSegment.SegmentType == CommandSegmentType.Binary) || (commandSegment.SegmentType == CommandSegmentType.Condition))
                    {
                        condition = GenerateConditionCommand(commandSegment);
                    }
                }
                #region condition
                if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                {
                    if (command != null)
                    {
                        command = commandBuilder.AppendConditionCommand(command, condition);
                    }
                    else
                    {
                        command = condition;
                    }
                    finalSegmentType |= CommandSegmentType.Condition;
                }
                #endregion
                #region sorting
                CommonCommand sorting = GenerateSortingCommand();
                if ((sorting != null) && (!string.IsNullOrEmpty(sorting.CommandText)))
                {
                    if (command != null)
                    {
                        command = new CommonCommand(command.CommandText + sorting.CommandText);
                    }
                    else
                    {
                        command = sorting;
                    }
                    finalSegmentType |= CommandSegmentType.Sorting;
                }
                #endregion
                this.commandSegment = new CommandSegment(command, finalSegmentType);
            }
        }

        private CommonCommand GenerateConditionCommand(CommandSegment commandSegment)
        {
            CommonCommand condition = null;
            if (commandSegment != null)
            {
                if (commandSegment.SegmentType == CommandSegmentType.Condition)
                {
                    condition = commandSegment.CommonCommand;
                }
                else if (commandSegment.SegmentType == CommandSegmentType.Binary)
                {
                    string conditionCommandText = string.Format("<Where>{0}</Where>", commandSegment.CommonCommand.CommandText);
                    condition = new CommonCommand(conditionCommandText);
                }
            }
            return condition;
        }

        private CommonCommand GenerateSortingCommand()
        {
            CommonCommand sortingCommand = new CommonCommand();
            string commandText = string.Empty;
            if ((this.sortingExpressions != null) && (this.sortingExpressions.Count > 0))
            {
                StringBuilder sortingExpressionStringBuilder = new StringBuilder();
                sortingExpressionStringBuilder.Append("<OrderBy>");
                foreach (RaisingStudio.Data.Expressions.SortingExpression sortingExpression in this.sortingExpressions)
                {
                    string sortingExpressionString = GetQuerySortingExpressionString(sortingExpression);
                    sortingExpressionStringBuilder.Append(sortingExpressionString);
                }
                sortingExpressionStringBuilder.Append("</OrderBy>");
                commandText = sortingExpressionStringBuilder.ToString();
            }
            sortingCommand.CommandText = commandText;
            return sortingCommand;
        }

        private string GetQuerySortingExpressionString(RaisingStudio.Data.Expressions.SortingExpression sortingExpression)
        {
            return string.Format("<FieldRef Name='{0}' Ascending='{1}'/>", sortingExpression.ColumnName, sortingExpression.SortingDirection == RaisingStudio.Data.Expressions.SortingDirection.Ascending ? "TRUE" : "FALSE");
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b != null)
            {
                string operratorString;
                switch (b.NodeType)
                {
                    case ExpressionType.Equal:
                        {
                            operratorString = "Eq";
                            if ((b.Right is ConstantExpression) && ((b.Right as ConstantExpression).Value == null))
                            {
                                return this.VisitIsNull(b);
                            }
                            break;
                        }
                    case ExpressionType.NotEqual:
                        {
                            operratorString = "Neq";
                            if ((b.Right is ConstantExpression) && ((b.Right as ConstantExpression).Value == null))
                            {
                                return this.VisitIsNotNull(b);
                            }
                            break;
                        }
                    case ExpressionType.GreaterThan:
                        {
                            operratorString = "Gt";
                            break;
                        }
                    case ExpressionType.GreaterThanOrEqual:
                        {
                            operratorString = "Geq";
                            break;
                        }
                    case ExpressionType.LessThan:
                        {
                            operratorString = "Lt";
                            break;
                        }
                    case ExpressionType.LessThanOrEqual:
                        {
                            operratorString = "Leq";
                            break;
                        }

                    case ExpressionType.AndAlso:
                        {
                            operratorString = "And";
                            break;
                        }
                    case ExpressionType.OrElse:
                        {
                            operratorString = "Or";
                            break;
                        }

                    default:
                        {
                            throw new NotSupportedException(b.NodeType + " is not supported.");
                        }
                }

                this.Visit(b.Left);
                this.Visit(b.Right);

                CommonCommand right = this.segmentStack.Pop().CommonCommand;
                CommonCommand left = this.segmentStack.Pop().CommonCommand;

                string commandText = String.Format("<{1}>{0}{2}</{1}>", left, operratorString, right);
                CommonCommand command = new CommonCommand(commandText);

                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Binary });
            }
            return b;
        }

        private Expression VisitIsNull(BinaryExpression b)
        {
            this.Visit(b.Left);
            CommonCommand left = this.segmentStack.Pop().CommonCommand;            
            string commandText = String.Format("<IsNull>{0}</IsNull>", left);
            CommonCommand command = new CommonCommand(commandText);
            this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Binary });
            return b;
        }

        private Expression VisitIsNotNull(BinaryExpression b)
        {
            this.Visit(b.Left);
            CommonCommand left = this.segmentStack.Pop().CommonCommand;
            string commandText = String.Format("<IsNotNull>{0}</IsNotNull>", left);
            CommonCommand command = new CommonCommand(commandText);
            this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Binary });
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            if (c != null)
            {
                // TODO: get value expression.
                CommonCommand command = new CommonCommand(GetQueryValueExpressionString(GetQueryValueExpressionTypeString(c.Type), c.Value));
                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Parameter });
            }
            return c;
        }

        private string GetQueryValueExpressionTypeString(System.Type type)
        {
            if (type == typeof(string))
            {
                return "Text";
            }
            else if (type == typeof(int))
            {
                return "Integer";
            }
            else if (type == typeof(DateTime))
            {
                return "DateTime";
            }
            else if (type == typeof(bool))
            {
                return "Boolean";
            }
            else if (type == typeof(decimal))
            {
                return "Number";
            }
            else if (type == typeof(Guid))
            {
                return "Guid";
            }
            else
            {
                return "Text";
            }
        }


        private string GetQueryValueExpressionString(string type, object value)
        {
            string valueString = null;
            if (value != null)
            {
                if (value is DateTime)
                {
                    DateTime dateTime = (DateTime)value;
                    valueString = CreateISO8601DateTimeFromSystemDateTime(dateTime);
                    if (dateTime.Kind == DateTimeKind.Utc)
                    {
                        return string.Format("<Value Type='{0}' IncludeTimeValue='TRUE' StorageTZ='TRUE'>{1}</Value>", type, valueString);
                    }
                    else
                    {
                        return string.Format("<Value Type='{0}' IncludeTimeValue='TRUE'>{1}</Value>", type, valueString);
                    }
                }
                else if (value is string)
                {
                    // TODO: html encoding.                            
                    valueString = HttpUtility.HtmlEncode(value as string);
                }
                else
                {
                    // TODO: html encoding.
                    valueString = HttpUtility.HtmlEncode(value.ToString());
                }
            }
            else
            {
                // TODO:
            }
            // values.
            return string.Format("<Value Type='{0}'>{1}</Value>", type, valueString);
        }

        private string CreateISO8601DateTimeFromSystemDateTime(DateTime dateTime)
        {
            return dateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m != null)
            {
                PropertyInfo propertyInfo = m.Member as PropertyInfo;
                if (propertyInfo != null)
                {
                    this.segmentStack.Push(new CommandSegment { CommonCommand = new CommonCommand(string.Format("<FieldRef Name='{0}'/>", propertyInfo.Name)), SegmentType = CommandSegmentType.Column });
                    this.columnStack.Push(new RaisingStudio.Data.Expressions.ColumnExpression(propertyInfo.Name));
                }
            }
            return m;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m != null)
            {
                MethodInfo methoInfo = m.Method;
                if (methoInfo != null)
                {
                    switch (methoInfo.Name)
                    {
                        #region order
                        case "OrderBy":
                        case "ThenBy":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    this.Visit(m.Arguments[1]);
                                    {
                                        if (this.columnStack.Count > 0)
                                        {
                                            RaisingStudio.Data.Expressions.ColumnExpression columnExpression = this.columnStack.Pop();
                                            this.sortingExpressions.Add(new RaisingStudio.Data.Expressions.SortingExpression(columnExpression));
                                            this.segmentStack.Pop();
                                        }
                                    }
                                }
                                return m;
                            }
                        case "OrderByDescending":
                        case "ThenByDescending":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    this.Visit(m.Arguments[1]);
                                    {
                                        if (this.columnStack.Count > 0)
                                        {
                                            RaisingStudio.Data.Expressions.ColumnExpression columnExpression = this.columnStack.Pop();
                                            this.sortingExpressions.Add(new RaisingStudio.Data.Expressions.SortingExpression(columnExpression, RaisingStudio.Data.Expressions.SortingDirection.Descending));
                                            this.segmentStack.Pop();
                                        }
                                    }
                                }
                                return m;
                            }
                        #endregion
                        #region select
                        case "Select":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    RaisingStudio.Data.Expressions.ColumnExpression[] columns = GetColumnExpressions(m.Arguments[1]);
                                    if (columns != null)
                                    {
                                        this.columnExpressions.AddRange(columns);
                                    }
                                }
                                return m;
                            }
                        #endregion
                        #region function
                        case "Count":
                            {
                                this.Visit(m.Arguments[0]);
                                CommonCommand command = this.commandBuilder.GetDataCountCommand();
                                commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                CommonCommand condition = GenerateConditionCommand(commandSegment);
                                if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                {
                                    command = this.commandBuilder.AppendConditionCommand(command, condition);
                                }
                                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                return m;
                            }
                        case "Average":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetAvgDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        case "Max":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetMaxDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        case "Min":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetMinDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        case "Sum":
                            {
                                this.Visit(m.Arguments[0]);
                                if ((this.columnExpressions != null) && (this.columnExpressions.Count > 0))
                                {
                                    string columnName = (string)this.columnExpressions[0];
                                    CommonCommand command = this.commandBuilder.GetSumDataCommand(columnName);
                                    commandSegment = this.segmentStack.Count > 0 ? this.segmentStack.Pop() : null;
                                    CommonCommand condition = GenerateConditionCommand(commandSegment);
                                    if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
                                    {
                                        command = this.commandBuilder.AppendConditionCommand(command, condition);
                                    }
                                    this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Select });
                                }
                                return m;
                            }
                        #endregion
                        #region sharepoint
                        case "StartsWith":
                            {
                                // TODO:
                                this.Visit(m.Arguments[0]);
                                this.Visit(m.Object);

                                CommonCommand left = this.segmentStack.Pop().CommonCommand;
                                CommonCommand right = this.segmentStack.Pop().CommonCommand;

                                string operratorString = "BeginsWith";                                
                                string commandText = String.Format("<{1}>{0}{2}</{1}>", left, operratorString, right);
                                CommonCommand command = new CommonCommand(commandText);

                                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Binary });
                                return m;
                            }
                        case "Contains":
                            {
                                // TODO:
                                this.Visit(m.Arguments[0]);
                                this.Visit(m.Object);

                                CommonCommand left = this.segmentStack.Pop().CommonCommand;
                                CommonCommand right = this.segmentStack.Pop().CommonCommand;

                                string operratorString = "Contains";
                                string commandText = String.Format("<{1}>{0}{2}</{1}>", left, operratorString, right);
                                CommonCommand command = new CommonCommand(commandText);

                                this.segmentStack.Push(new CommandSegment { CommonCommand = command, SegmentType = CommandSegmentType.Binary });
                                return m;
                            }
                        #endregion
                    }
                }
            }
            // TODO: call method.
            return base.VisitMethodCall(m);
        }
    }
}
